
;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 
;                            SYSTYPE 
;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 
;    Determine the system type, diskettes and hard drive 
;    information and display type and information.
; 
;    (c) Copyright 1994  Frank van Gilluwe  All Rights Reserved. 

include undocpc.inc


cseg    segment para public
        assume  cs:cseg, ds:cseg, ss:stacka

;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ DATA AREA ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ

; ÄÄÄÄÄ general data

        db      'SYSTYPE v1.00 '
        db      '(c) 1994 Frank van Gilluwe',0

syshead db      CR, LF, CR, LF
        db      'SYSTEM ANALYSIS             '
        db      '                      v1.00 (c) 1994 FVG'
        db      CR, LF
        db      'ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ'
        db      'ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ'
        db      CR, LF, CR, LF, '$'

systyp  db      'System type:    '
systypm db      '                                               '
        db      CR, LF, CR, LF, '$'

sysmsg  db      0
        db      'PC (8088 based)                               '
sysend  db      1
        db      'XT (8088 based)                               '
        db      2 
        db      'PC convertible (8088 based)                   '
        db      3 
        db      'PC jr (8088 based)                            '
        db      4 
        db      'unknown pre-80286                             '
        db      8 
        db      'XT (80286 based)                              '
        db      10h
        db      'AT / ISA (Industry Standard Architecture)     '
        db      20h
        db      'EISA (Extended Industry Standard Architecture)'
        db      40h
        db      'MCA (Micro Channel Architecture)              '
        db      0FFh
        db      'unknown                                       '

no_drvs db      'No diskette drives ', CR, LF, '$'

drv     db      'Diskette '
drvltr  db               'a:     '
drvtext db      '360KB   5.25" (40 tracks, 9 sectors per track) '
        db      CR, LF, '$'

drvtyps db      '360KB   5.25" (40 tracks, 9 sectors per track) '
drvend  db      '1.2MB   5.25" (80 tracks, 15 sectors per track)'
        db      '720KB   3.5"  (40 tracks, 9 sectors per track) '
        db      '1.44MB  3.5"  (80 tracks, 18 sectors per track)'
        db      '2.88MB  3.5"  (80 tracks, 36 sectors per track)'
        db      '320KB   5.25" (40 tracks, 8 sectors per track) '
        db      'unknown type                                   '

blank_ln db     CR, LF, '$'

no_hdrvs db     'No hard drives ', CR, LF, '$'

hdrv    db      'Hard Drive '
hdrvnum db                 '0:   '
hdrvtxt db      'PC/XT Type Controller    '
        db      CR, LF
        db      '                  Total size = '
hsize   db                                     '         '
        db      '(with diagnostic cylinder)'
        db      CR, LF
        db      '                '
hcyldr  db                      '      Cylinders,'
hheads  db      '      Heads,'
hsector db                  '      Sectors per track'
        db      CR, LF, '$'


hdrvtyp db      'PC/XT Type Controller    '
hdrvend db      'AT Type Controller       '
        db      'SCSI Type Controller     '
        db      'PS/2 ESDI Type Controller'
        db      'Unknown Controller Type  '

vidmsg  db      CR, LF
        db      'Video Adapter:  $'
vidtype db      '                                           '
        db      CR, LF, '$'

vidmfg  db      'Video Vendor:   '
vidmstr db      '                                           '
        db      CR, LF, '$'


vtype   db      'MDA$     '        ; 0
vtypend db      'HGA$     '        ; 1
        db      'CGA$     '        ; 2    
        db      'MCGA$    '        ; 3 
        db      'EGA$     '        ; 4 
        db      'VGA$     '        ; 5 
        db      'SVGA$    '        ; 6 
        db      'XGA$     '        ; 7 
        db      'VESA XGA$'        ; 8 

vcolor  db      ', programs should use color attributes.     '
        db      CR, LF, '$'
vcolore db      ', programs should use monochrome attributes.'
        db      CR, LF, '$'
        db      ', programs should use grayscale attributes. '
        db      CR, LF, '$'

old_int6_seg dw 0                  ; temp storage for old int 6
old_int6_off dw 0                  ;  vector (bad opcode)
badoff       dw 0                  ; temp return offset if bad offset
                                   ;  interrupt 6 called

;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ CODE START ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ

systype proc    far

start:  
        mov     ax, cs
        mov     ds, ax
        mov     es, ax
        OUTMSG  syshead            ; display system type

; --- Find system type

        call    sysvalue           ; get system value in al
        mov     cx, offset sysend - offset sysmsg
        mov     si, offset sysmsg
syst_loop:
        cmp     byte ptr [si], 0FFh  ; no match ?
        je      syst_skp1          ; exit if so
        cmp     al, [si]           ; system type match ?
        je      syst_skp1          ; exit if so
        add     si, cx
        jmp     syst_loop          ; loop until match or end

syst_skp1:
        inc     si
        dec     cx
        mov     di, offset systypm ; where to insert type text
        cld
        rep     movsb              ; xfer message
        OUTMSG  systyp             ; display system type

; --- Find number of Diskette drives

syst_skp2:
        mov     dl, 0              ; drive 0 first
syst_loop2:
        push    dx
        call    dsktype            ; drive type in al
        cmp     al, 0 
        je      syst_skp3          ; jump if no drive
        dec     al                 ; zero based
        mov     cx, offset drvend - offset drvtyps
        mul     cl                 ; ax = cl * al
        add     ax, offset drvtyps
        mov     si, ax
        mov     di, offset drvtext
        cld
        rep     movsb              ; xfer string
        OUTMSG  drv                ; display drive message
        pop     dx
        inc     dl                 ; handle next drive
        inc     [drvltr]           ; next drive letter
        cmp     dl, 4
        jb      syst_loop2
        jmp     syst_skp4

syst_skp3:
        pop     dx
        cmp     dl, 1              ; were we looking at a: or b:
        jae     syst_skp4          ; jump if there is one drive
        OUTMSG  no_drvs            ; no diskette drives

; --- now check hard drive information

syst_skp4:
        OUTMSG  blank_ln           ; insert blank line
        mov     dl, 80h            ; drive 0 first
syst_loop3:
        push    dx
        call    hdsktype           ; drive type in al
        cmp     al, 0 
        jne     syst_skp5
        jmp     syst_skp6          ; jump if no drive

syst_skp5:
        push    ax
        mov     ax, cx             ; get cylinders
        mov     di, offset hcyldr
        mov     word ptr [di], '  '  
        mov     word ptr [di+2], '  '  
        mov     bl, 1              ; no left justification
        call    decw               ; insert cylinders (decimal)

        xor     ah, ah
        mov     al, dh             ; get heads
        mov     hheads+3, ' '      ; blank from prior use
        mov     di, offset hheads
        mov     bl, 1              ; no left justification
        call    decw               ; insert heads (decimal)

        xor     ah, ah
        mov     al, dl             ; get sectors
        mov     hsector+3, ' '     ; blank from prior use
        mov     di, offset hsector
        mov     bl, 1              ; no left justification
        call    decw               ; insert sectors (decimal)

        mov     al, dh
        mul     dl                 ; ax = heads * sectors
        mul     cx                 ; dx:ax = total bytes/512
        mov     cx, 11             ; divide to get Megabytes
syst_loop4:
        shr     dx, 1              ; shift right into carry
        rcr     ax, 1              ; rotate right with carry
        loop    syst_loop4
        mov     di, offset hsize
        xor     bl, bl             ; left justification
        call    decw               ; insert total size in MB
        mov     word ptr [di], 'M '  ; insert " MB   "
        mov     word ptr [di+2], ' B'  
        mov     word ptr [di+4], '  '  

        pop     ax                 ; get type back
        dec     al                 ; zero based
        mov     cx, offset hdrvend - offset hdrvtyp
        mul     cl                 ; ax = cl * al
        add     ax, offset hdrvtyp
        mov     si, ax
        mov     di, offset hdrvtxt
        cld
        rep     movsb              ; xfer string

        OUTMSG  hdrv               ; display hard drive message
        pop     dx
        inc     dl                 ; handle next drive
        inc     [hdrvnum]          ; next drive number
        cmp     dl, 88h            ; test for up to 8 drives
        jae     syst_skp7          ; exit if done
        jmp     syst_loop3

syst_skp6:
        pop     dx
        cmp     dl, 81h            ; looking at drive 0 ?
        jae     syst_skp7          ; jump if there is one drive
        OUTMSG  no_hdrvs           ; no disk drives

; --- Find the video display type, attribute and possible vendor

syst_skp7:
        OUTMSG  vidmsg
        call    video_type         ; get video type information
        mov     bx, offset vtypend - offset vtype
        mul     bl                 ; ax = bl * video type
        mov     dx, offset vtype
        add     dx, ax             ; ds:dx = video type string
        mov     ah, 9
        int     21h                ; display string
        mov     al, ch             
        mov     bx, offset vcolore - offset vcolor
        mul     bl                 ; ax = bl * video attribute
        mov     dx, offset vcolor
        add     dx, ax
        mov     ah, 9
        int     21h                ; display attribute portion
        or      cl, cl             ; vendor string ? (cl = 1)
        jz      syst_done          ; jump if no vendor string

        cmp     byte ptr es:[si], 0
        je      syst_vendor        ; jump if no string
        mov     cx, 40             ; display a max of 40 chars
        mov     di, offset vidmstr
syst_vloop:
        mov     al, es:[si]        ; get string char
        or      al, al             ; end (zero)
        jz      syst_vendor
        mov     [di], al           ; transfer to output area
        inc     di
        inc     si
        loop    syst_vloop

syst_vendor:
        OUTMSG  vidmfg

syst_done:
        mov     ah,4Ch
        int     21h                ; exit with al return code
systype endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    SYSTEM TYPE DETECTION SUBROUTINE
;       Determine the type of system the software is running 
;       on.
;
;       Called with:    nothing
;
;       Returns:        al = System type
;                             0 if PC (8088 based)
;                             1 if XT (8088 based)
;                             2 if PC convertible (8088 based)
;                             3 if PC jr (8088 based)
;                             4 other pre-80286 based machine
;                             8 if XT (80286 based)
;                            10h if AT or ISA
;                            20h if EISA 
;                            40h if MCA 
;
;       Regs used:      ax, bx
;                       eax, ebx (386 or later)
;
;       Subs called:    cpuvalue

sysvalue proc    near
        push    cx
        push    dx
        push    es

        call    far ptr cpuvalue   ; get the cpu type in al
        mov     cl, al             ; save cpu number (0 to 5)

; Avoid directly reading BIOS ROM, because a few memory managers 
; like 386MAX alter bytes at the end of the BIOS. 

        push    cx                 ; save cpu number on stack
        mov     ah, 0C0h
        int     15h                ; get BIOS config data es:bx
        pop     cx
        jc      sys_skp1           ; jump if no config support
                                   ;   (old BIOS)
        mov     dl, es:[bx+2]      ; get model byte
        mov     dh, es:[bx+3]      ; get submodel byte

        mov     al, 40h            ; assume MCA
        test    byte ptr es:[bx+5], 2  
        jnz     sys_Exit           ; exit if MCA
        jmp     sys_skp2

; we only get here on older PCs in which a memory manager 
;  can not be run

sys_skp1:                          ; ok, get BIOS model directly 
        mov     ax, 0F000h
        mov     es, ax             ; point into system BIOS
        mov     dx, es:[0FFFEh]    ; get model & submodel byte

; now use the model and submodel bytes im DX to determine machine

sys_skp2:
        xor     al, al             ; assume PC (al=0)
        cmp     dl, 0FFh
        je      sys_Exit           ; jump if PC     
        inc     al                 ; assume XT (al=1)
        cmp     dl, 0FEh
        je      sys_Exit           ; jump if XT
        cmp     dl, 0FBh
        je      sys_Exit           ; jump if XT
        inc     al                 ; assume PC convertible (al=2)
        cmp     dl, 0F9h
        je      sys_Exit           ; jump if convertible
        inc     al                 ; assume PCjr (al=3)
        cmp     dl, 0FDh
        je      sys_Exit           ; jump if PCjr
        inc     al                 ; assume other pre-286 (al=4)
        cmp     cl, 2              ; cl=CPU type - pre-286 ?
        jb      sys_Exit           ; jump so
        ja      sys_skp3           ; jump if 386 or above

; possible a 286 XT - use the model and submodel bytes to 
;  determine

        mov     al, 8              ; assumption for 286XT
        cmp     dx, 02FCh          ; model code for 286XT ?
        je      sys_exit           ; jump if so

; check if EISA system by looking for the "EISA" string at 
;  address F000:FFD9

sys_skp3:
        mov     ax, 0F000h
        mov     es, ax
        mov     al, 10h            ; assume a standard AT/ISA
        cmp     word ptr es:[0FFD9h], 'IE'
        jne     sys_exit           ; jump if not EISA
        cmp     word ptr es:[0FFDBh], 'AS'
        jne     sys_exit           ; jump if not EISA
        mov     al, 20h            ; EISA machine
sys_Exit:
        pop     es
        pop     dx
        pop     cx
        ret
sysvalue endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    DISKETTE DRIVE TYPE DETECTION SUBROUTINE
;       Determine the type of diskette drive
;
;       Called with:    dl = drive to check (0=a:, 1=b:, etc.)
;
;       Returns:        al = drive type
;                               0 = no drive
;                               1 = 360KB
;                               2 = 1.2MB
;                               3 = 720KB
;                               4 = 1.44MB
;                               5 = 2.88MB
;                               6 = 320KB  (obsolete)
;                               7 = unknown type
;
;       Regs used:      ax

dsktype proc    near
        push    bx
        push    cx
        push    dx
        push    es

        int     11h                ; equipment check
        test    ax, 1              ; any drives ?
        jz      dskt_none          ; jump if not
        and     al, 0C0h           ; get # of drives bits
        mov     cl, 2
        rol     al, cl             ; convert to # of drives
        cmp     al, dl             ; is drive available ?
        jb      dskt_none          ; jump if not

; first we'll try getting the drive parameters from the BIOS

        mov     ah, 8
        int     13h                ; get drive parameters
        jc      dskt_skp1          ; jump if failed
        mov     al, bl             ; get drive type
        cmp     al, 5              ; unknown type if > 5
        jbe     dskt_done
        mov     al, 7              ; set to unknown type
        jmp     dskt_done

; Older systems can't supply drive info, so use diskette table.
;   First the drive is reset.  This forces systems that change 
;   the diskette parameter pointer for different drive types
;   to update the pointer to the correct drive type. Interrupt 
;   vector 1Eh now points to the correct diskette parameter 
;   table.  Only 360KB and 1.2MB drives should get here.

dskt_skp1:
        xor     ah, ah
        int     13h                ; reset drive 
        xor     ax, ax
        mov     es, ax
        mov     bx, es:[1Eh*4]     ; get offset of table
        mov     ax, es:[1Eh*4+2]   ; get segment of table 
        mov     es, ax             ; es:bx points to table
        mov     ah, es:[bx+4]      ; get sectors per track
        mov     al, 1              ; assume 360K
        cmp     ah, 9              ; 9 sectors per track ?
        je      dskt_done          ; jump if so
        inc     al
        cmp     ah, 15             ; 15 sectors per track
        je      dskt_done          ; jump if so
        mov     al, 6              ; unknown type
        cmp     ah, 8              ; 8 sectors per track
        je      dskt_done          ; jump if so
        inc     al                 ; al=7, unknown type
        jmp     dskt_done

dskt_none:
        xor     al, al
dskt_done:
        pop     es
        pop     dx
        pop     cx
        pop     bx
        ret
dsktype endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    HARD DISK DRIVE TYPE DETECTION SUBROUTINE
;       Determine if a hard drive is attached, its likely type, 
;       and the capacity of that drive. 
; 
;       Called with:    ds = cs
;                       dl = drive to check, 80h=drive 0, etc.
; 
;       Returns:        al = drive type 
;                               0 = no drive or no controller
;                               1 = XT type controller
;                               2 = AT type controller 
;                               3 = SCSI type controller
;                               4 = PS/2 ESDI type controller
;                               5 = unknown controller type
;                       cx = total number of cylinders (includes
;                              the diagnostic cylinder)
;                       dh = total number of heads (1 to 64)
;                       dl = number of 512 byte sectors
;       Regs used:      ax

; local data for routine

hd_parm_seg     dw      0               ; pointer to disk
hd_parm_off     dw      0               ;  parameter table
hd_cylinder     dw      0               ; temp # from table
ESDIbuf         db      1024 dup (0)    ; ESDI info buffer

hdsktype proc   near
        push    bx
        push    es
        xor     bp, bp             ; used for temp drive type
        cmp     dl, 80h            ; at least 80h ?
        jb      hdsk_skp1          ; exit if not

; first we will get the number of drives attached

        push    dx
        mov     dl, 80h            ; ask for drive 0
        mov     ah, 8
        int     13h                ; read disk drive parameters
        pop     ax
        add     dl, 7Fh
        cmp     al, dl             ; drive for this number ?
        jbe     hdsk_skp2 
hdsk_skp1:
        jmp     hdsk_none          ; jump if out of range

; now determine the controller/disk type

hdsk_skp2:
        inc     bp                 ; bp=1 assume XT type drive
        mov     dl, al             ; dl = drive number
        push    dx
        mov     ah, 15h
        int     13h
        pop     dx
        cmp     ah, 1              ; invalid request status ?
        je      hdsk_skp4          ; if so, XT type & exit
        inc     bp                 ; set type to AT
        cmp     ah, 3              ; confirm valid hard disk
        je      hdsk_skp3 
        jmp     hdsk_none          ; exit if not

; let's check if it's a drive which does not use the drive 
;   parameter table like SCSI and some ESDI drives.

hdsk_skp3:
        inc     bp                 ; assume SCSI
        cmp     dl, 82h            ; if 82h to 87h, assume SCSI
        jb      hdsk_skp5
hdsk_skp4:
        jmp     hdsk_info          ; jump if so

hdsk_skp5:
        mov     bx, 4*41h          ; assume ptr to vector 41h
        cmp     dl, 80h            ; drive 0 ?
        je      hdsk_skp6          ; jump if so
        mov     bx, 4*46h          ; pointer to vector 46h
hdsk_skp6:
        xor     ax, ax
        mov     es, ax
        mov     si, es:[bx]        ; offset of parameter table
        mov     ax, es:[bx+2]      ; segment of parameter table
        mov     [hd_parm_seg], ax  ; save pointer
        mov     [hd_parm_off], si
        mov     es, ax             ; es:si ptr to table

; we now have a pointer to the disk parameter table, but there 
;   are two types!  Check if word at offset 3 is 0 or -1, then
;   use normal table (cylinder at offset 0).  Otherwise use 
;   PS/2 style, where cylinder is at offset 19h.

        mov     ax, es:[si+3]      ; get word to check
        cmp     ax, 0
        je      hdsk_skp7
        cmp     ax, -1
        je      hdsk_skp7
        add     si, 19h            ; adjust to PS/2 style
hdsk_skp7:
        mov     ax, es:[si]        ; get cylinders from table
        mov     [hd_cylinder], ax  ; save 
        cmp     ax, 0              ; invalid # of cylinders ?
        jne     hdsk_skp8          ; jump if ok (non-zero)
        jmp     hdsk_info          ; we will assume SCSI

hdsk_skp8:
        dec     bp                 ; assume AT type
        push    dx
        mov     ah, 8
        int     13h                ; get disk parameters
        call    hdconvert          ; convert into useful values
        pop     dx
        cmp     ax, [hd_cylinder]  ; are they the same ?
        jne     hdsk_skp9
        jmp     hdsk_info          ; if so, likely AT type

; Not likely AT type, since cylinders do not match up!
;  First we'll try an older Future Domain SCSI test

; Future Domain SCSI test - Interrupt 13h, function 18h will
;  return invalid command with DL >= 80h, if no Future Domain
;  SCSI card is present

hdsk_skp9:
        inc     bp                 ; assume SCSI
        push    dx
        mov     ah, 18h
        int     13h                ; Future Domain SCSI ?
        pop     dx
        jc      hdsk_skp10         ; jump if not
        cmp     ax, 4321h          ; confirmation number
        jne     hdsk_skp10
        jmp     hdsk_info

; Now check if possible PS/2 ESDI drive

hdsk_skp10:
        push    cs
        pop     es
        push    dx
        mov     ax, 1B0Ah
        mov     bx, offset ESDIbuf
        int     13h                ; Get ESDI config
        pop     dx
        jc      hdsk_skp11         ; jump if not PS/2 ESDI
        cmp     bx, offset ESDIbuf
        jne     hdsk_skp11
        mov     ax, cs
        mov     bx, es
        cmp     ax, bx             ; is CS = ES ?
        jne     hdsk_skp11         ; jump if not
        inc     bp
        jmp     hdsk_info

; General SCSI test (not drive specific)

hdsk_skp11:
        xor     ax, ax
        mov     es, ax
        cmp     word ptr es:[4*4Fh+2], 0  ; vector valid ?
        je      hdsk_skp12         ; jump if not
        push    dx
        mov     ax, 8200h
        mov     cx, 8765h
        mov     dx, 0CBA9h
        int     4Fh                ; check for SCSI CAM
        cmp     dx, 5678h
        pop     dx
        jne     hdsk_skp12         ; jump if not
        cmp     ah, 0
        jne     hdsk_skp12         ; jump if not
        cmp     cx, 9ABCh
        jne     hdsk_skp12         ; jump if not
        jmp     hdsk_info

hdsk_skp12:
        inc     bp                 ; Indicate unknown 
                                   ;  controller type

; now get the disk parameter for the specified drive

hdsk_info:
        mov     ah, 8
        int     13h                ; read disk drive parameters
        call    hdconvert          ; convert to useful value
        mov     dl, cl
        mov     cx, ax             ; put in proper registers

        mov     ax, bp             ; get drive type
        jmp     hdsk_exit

hdsk_none:
        xor     al, al             ; no drive with this number
        xor     cx, cx
        xor     dx, dx
hdsk_exit:
        pop     es
        pop     bx
        ret
hdsktype endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    HARD DISK CONVERT 
;
;       The cylinder number is in cx and part of dh.  This 
;       routine properly combines them back into a 12 bit 
;       cylinder number in AX. The number is increased by 
;       one for the diagnostic cylinder, and by one more, since 
;       the number is zero based. The upper two bits in CL are 
;       cleared to get the sector number.   The upper two bits 
;       of DH are cleared to get the head number.  One is added
;       to the head number, since it is zero based.
;
;       Called with:    ch = lower 8 bits of cylinder number
;                       cl upper two bits are 
;                             bits 9 & 8 of cylinder
;                          lower six bits is sector number
;                       dh upper two bits are 
;                             bits 11 & 10 of cylinder
;                          lower six bits are head number
;
;       Returns:        ax = total combined cylinders
;                       cl = sector number
;                       dh = total heads
;
;       Regs used:      cx, dh

hdconvert proc   near
        push    dx
        push    cx
        mov     ax, cx             ; get fragmented cylinder #
        rol     al, 1
        rol     al, 1
        and     al, 3
        xchg    al, ah             ; cx = # of cylinders
        and     dh, 0C0h           ; get undoc cylinder bits
        mov     cl, 4
        shl     dh, cl             ; shift to 10 & 9th bits
        or      ah, dh             
        add     ax, 2              ; ax = real max cylinders
        pop     cx
        and     cx, 3Fh            ; mask for sector number
        pop     dx
        and     dh, 3Fh            ; mask for heads
        inc     dh                 ; adjust for head count
        ret
hdconvert endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    VIDEO TYPE DETECT 
;
;       Find the video type, type attributes and possible 
;       vendor string.  This routine assumes the display is
;       in a text mode to determine the attribute byte CL.
;
;       The attributes option indicates what attributes a 
;       program should use, color, monochrome, or grayscales.
;
;       Called with:    nothing
;
;       Returns:        al = video type
;                             0 = MDA
;                             1 = HGA
;                             2 = CGA
;                             3 = MCGA
;                             4 = EGA
;                             5 = VGA
;                             6 = SVGA
;                             7 = XGA
;                             8 = VESA XGA
;                       ch = attribute type 
;                             0 = color
;                             1 = monochrome
;                             2 = gray scale (some MCGA or VGA+)
;                       cl = vendor string present in es:bx
;                             0 = no vendor string
;                             1 = vendor string
;                       es:si = vendor string, zero terminated
;                               (if cl = 1)
;
;       Regs used:      ax, cx, si, es

infobuf db      256 dup (0)        ; buffer for video info
hercstr db      'Hercules', 0      ; Vendor string if Hercules

video_type proc   near
        push    bx
        push    dx
        push    di
        push    bp
        mov     bp, 4              ; bp = temp video type, 4=EGA

; --- check if EGA or later using get video information function

        mov     ah, 12h            ; get video info function
        mov     bh, 5Ah            ; test value
        mov     bl, 10h            ; subfunction EGA+ info
        int     10h
        cmp     bh, 1              ; must be 0, color or 1, mono
        ja      below_EGA          ; jump if not EGA+

; --- it is an EGA or later, so now test for VGA

        push    bx                 ; save color info for later
        mov     ax, 1A00h          ; get display code
        int     10h
        cmp     al, 1Ah            ; is function supported ?
        je      vid_VGA            ; if so, at least a VGA
        jmp     type_found         ; jump if not (must be EGA)

; --- at least a VGA, now test for SVGA

vid_VGA:
        inc     bp                 ; assume VGA (5)
        push    cs
        pop     es
        mov     di, offset infobuf ; buffer for video info
        mov     ax, 4F00h          ; return SVGA info
        int     10h
        cmp     al, 4Fh            ; is function supported ?
        jne     XGA_test           ; jump if not SVGA
        inc     bp                 ; assume SVGA (6)

; --- at least a VGA/SVGA, now test for XGA/VESA XGA

XGA_test:
        mov     ax, 1F00h          ; get XGA information size
        int     10h
        cmp     al, 1Fh            ; is function supported ?
        jne     type_found         ; jump if not, is VGA or SVGA
        mov     bp, 7              ; set to XGA

; --- at least a XGA, now test for VESA XGA

        mov     di, offset infobuf ; buffer for video info
        mov     ax, 4E00h          ; return VESA XGA info
        int     10h
        cmp     ax, 004Eh          ; is function supported ?
        jne     type_found         ; jump if not
        inc     bp                 ; VESA XGA (8)
        jmp     type_found

; --- arrives here if adapter is below an EGA

below_EGA:
        mov     bp, 3              ; assume MCGA (3)
        mov     ax, 1A00h          ; get display code
        int     10h
        cmp     al, 1Ah            ; is function supported ?
        jne     vid_not_MCGA       ; jump if not MCGA
        mov     ah, 0Fh
        int     10h                ; get video mode
        mov     cx, 100h           ; assume mono, no vendor
        cmp     al, 7
        je      vid_chk_gray       ; if mono, check gray
        mov     ch, 0              ; looks like color!
        je      vid_chk_gray       ; check gray scales

; --- not MCGA, test for CGA or MDA/HGA

vid_not_MCGA:
        dec     bp                 ; assume CGA (2)
        mov     ah, 0Fh            ; get video mode
        int     10h
        cmp     al, 7              ; mode 7 monochrome ?
        je      vid_mono_type      ; jump if so
        xor     cx, cx             ; return color, no vendor
        jne     vid_mono_type
        jmp     vid_mono_chk       ; must be CGA

; --- Must be MDA or HGA, so find out which.  The HGA 
;     (Hercules Graphics Adapter) toggles an undefined bit on 
;     the MDA. Check to see ifthis bit changes state 10 times
;     or more.

vid_mono_type:
        dec     bp                 ; assume HGA (1)
        xor     bl, bl             ; start count at 0
        mov     dx, 3BAh           ; status port on HGA/MDA
        xor     ah, ah             ; ah used for prior status
        mov     cx, 0FFFFh         ; test for a long time
vid_loop:
        in      al, dx             ; read status port
        IODELAY          
        and     al, 80h            ; isolate HGA toggle bit
        cmp     al, ah             ; has it changed ?
        je      vid_no_toggle      ; jump if not
        inc     bl                 ; bit changed, increment
        cmp     bl, 10             ; more than 10 toggles ?
        jae     vid_herc           ; if so, it is a Hercules card
vid_no_toggle:
        loop    vid_loop           ; read again until cx 0

; --- falls through if MDA (bit does not toggle)

        dec     bp                 ; set to MDA (0)
        mov     cx, 100h           ; mono attribute, no vendor
        jmp     vid_exit

; --- adapter is Hercules type

vid_herc:
        mov     cx, 101h           ; mono attribute, vendor ok
        push    cs
        pop     es
        mov     si, offset hercstr ; set string to Hercules
        jmp     vid_exit

; --- For MCGA/EGA/VGA/XGA put the attribute type in CL

type_found:
        pop     bx                 ; get attribute type (0 or 1)
        mov     ch, bh
vid_chk_gray:
        mov     ax, 1A00h          ; read display code
        int     10h
        cmp     al, 1Ah            ; check if supported 
        jne     vid_string         ; jump if can't be grayscale
        cmp     bl, 7              ; grayscale monitor?
        je      vid_is_gray        ; jump if so
        cmp     bl, 0Bh            ; grayscale monitor?
        je      vid_is_gray        ; jump if so
        cmp     bp, 5              ; VGA or later ?

        mov     ax, 40h            ; BIOS data area
        mov     es, ax
        test    byte ptr es:[89h], 2  ; grayscale summing on ?
        jz      vid_string         ; not grayscale
vid_is_gray:
        mov     ch, 2              ; set ch to grayscale

vid_string:
        xor     cl, cl             ; assume no vendor string
        cmp     bp, 6              ; SVGA ?
        je      vid_skp1           ; jump if so
        cmp     bp, 8              ; VESA XGA ?
        jne     vid_mono_chk       ; jump if not
vid_skp1:
        mov     di, offset infobuf ; get buffer of SVGA info
        mov     si, cs:[di+6]      ; get offset and segment to 
        mov     ax, cs:[di+8]      ;   the vendor string
        mov     es, ax             ; es:bx points to string
        inc     cl                 ; vendor string valid

; The last check is made to see if video mode 2 is set, 
; indicating monochrome attributes should be used (if we 
; haven't already detected monochrome operation)

vid_mono_chk:
        cmp     ch, 0              ; set to color ?
        jne     vid_exit           ; jump if not
        mov     ah, 0Fh
        int     10h                ; get video mode
        and     al, 7Dh
        cmp     al, 0              ; video mode 0 or 2 ?
        jne     vid_exit           ; jump if not
        mov     ch, 1              ; use monochrome attributes

vid_exit:
        mov     ax, bp             ; return adapter type in al
        pop     bp
        pop     di
        pop     dx
        pop     bx
        ret
video_type endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    CPU IDENTIFICATION SUBROUTINE
;       Identify the CPU type, from 8088 to the Pentium.  Works 
;       even if the 386 or later CPU is in V86 mode.  Note that
;       interrupts are enabled at exit, even if they were 
;       disabled on entry.  If it is necessary to run this 
;       routine with interrupts disabled, just remove all CLI 
;       and STI instructions, so long as interrupts are 
;       always disabled before running.
;
;       Called with:    nothing
;
;       Returns:        al = CPU type
;                             0 if 8088/8086 or V20/V30
;                             1 if 80186/80188
;                             2 if 80286
;                             3 if 80386
;                             4 if 80486
;                             5 if Pentium
;                       ah =  bit 0 = 0 if CPUID unavailable
;                                     1 if CPUID ok
;                             bit 1 = 0 if not V20/V30
;                                     1 if NEC V20/V30
;
;       Regs used:      ax, bx (all)
;                       eax, ebx (386 or later)
;
;       Subs called:    hook_int6, restore_int6, bad_op_handler

.8086   ; all instructions 8088/8086 unless overridden later

cpuvalue proc    far
        push    cx
        push    dx
        push    ds
        push    es

; 8088/8086 test - Use rotate quirk - All later CPUs mask the CL
;   register with 0Fh, when shifting a byte by cl bits.  This 
;   test loads CL with a large value (20h) and shifts the AX
;   register right.  With the 8088, any bits in AX are shifted 
;   out, and becomes 0.  On all higher level processors, the
;   CL value of 20h is anded with 0Fh, before the shift.  This
;   means the effective number of shifts is 0, so AX is 
;   unaffected.

        mov     cl, 20h            ; load high CL value
        mov     ax, 1              ; load a non-zero value in AX
        shr     ax, cl             ; do the shift
        cmp     ax, 0              ; if zero, then 8088/86
        jne     up186              ; jump if not 8088/86

; V20/V30 test - It is now either a V20/V30 or a 8088.  I'll use
;   another undocumented trick to find out which.  On the 8088,
;   0Fh performs a POP CS.  On the V20/V30, it is the start of
;   a number of multi-byte instructions.  With the byte string
;   0Fh, 14h, C3h the CPU will perform the following:
;               8088/8086               V20/V30
;             pop     cs              set1   bl, cl  
;             adc     al, 0C3h

        xor     al, al             ; clear al and carry flag
        push    cs
        db      0Fh, 14h, 0C3h     ; instructions (see above)
        cmp     al, 0C3h           ; if al is C3h then 8088/8086
        jne     upV20
        mov     ax, 0              ; set 8088/8086 flag
        jmp     uP_Exit

upV20:
        pop     ax                 ; correct for lack of pop cs
        mov     ax, 200h           ; set V20/V30 flag
        jmp     uP_Exit

; 80186/80188 test - Check what is pushed onto the stack with a 
;   PUSH SP instruction.  The 80186 updates the stack pointer 
;   before the value of SP is pushed onto the stack.  With all
;   higher level processors, the current value of SP is pushed
;   onto the stack, and then the stack pointer is updated.
        
up186:  
        mov     bx, sp             ; save the current stack ptr
        push    sp                 ; do test
        pop     ax                 ; get the pushed value
        cmp     ax, bx             ; did SP change ?
        je      up286              ; if not, it's a 286+ 
        mov     ax, 1              ; set 80186 flag
        jmp     uP_Exit

; 80286 test A - We'll look at the top four bits of the EFLAGS 
;   register.  On a 286, these bits are always zero.  Later 
;   CPUs allow these bits to be changed.  During this test, 
;   We'll disable interrupts to ensure interrupts do not change 
;   the flags. 

up286: 
        cli                        ; disable interrupts
        pushf                      ; save the current flags

        pushf                      ; push flags onto stack 
        pop     ax                 ; now pop flags from stack
        or      ax, 0F000h         ; try and set bits 12-15 hi
        push    ax
        popf                       ; set new flags
        pushf
        pop     ax                 ; see if upper bits are 0

        popf                       ; restore flags to original
        sti                        ; enable interrupts
        test    ax, 0F000h         ; were any upper bits 1 ?
        jnz     up386              ; if so, not a 286

; 80286 test B - If the system was in V86 mode, (386 or higher) 
;   the POPF instruction causes a protection fault, and the 
;   protected mode software must emulate the action of POPF. If 
;   the protected mode software screws up, as occurs with a 
;   rarely encountered bug in Windows 3.1 enhanced mode, the 
;   prior test may look like a 286, but it's really a higher 
;   level processor. We'll check if the protected mode bit is 
;   on.  If not, it's guaranteed to be a 286.

.286P                              ; allow a 286 instruction
        smsw    ax                 ; get machine status word
        test    ax, 1              ; in protected mode ?
        jz      is286              ; jump if not (must be 286)

; 80286 test C - It's very likely a 386 or greater, but it is 
;   not guaranteed yet.  There is a small possibility the system 
;   could be in 286 protected mode so we'll do one last test. We 
;   will try out a 386 unique instruction, after vectoring the 
;   bad-opcode interrupt vector (int 6) to ourselves.  

        call    hook_int6          ; do it!
        mov     [badoff], offset upbad_op  ; where to go if bad
.386
        xchg    eax, eax           ; 32 bit nop (bad on 286)
        
        call    restore_int6       ; restore vector
        jmp     up386              ; only gets here if 386 
                                   ;  or greater!

; Interrupt vector 6 (bad opcode) comes here if system is a 
;   80286 (assuming the 286 protected mode interrupt 6 handler 
;   will execute the bad-opcode interrupt). 

upbad_op:
        call    restore_int6
is286:
        mov     ax, 2              ; set 80286 flag
        jmp     uP_Exit

; 80386 test - Bit 18 in EFLAGS is not settable on a 386, but is
;   changeable on the 486 and later CPUs.  Bit 18 is used to 
;   flag alignment faults. During this test, we'll disable 
;   interrupts to ensure no interrupt will change any flags.

.386                               ; allow 386 instructions

up386:
        cli                        ; disable interrupts
        pushfd                     ; push flags to look at
        pop     eax                ; get eflags
        mov     ebx, eax           ; save for later
        xor     eax, 40000h        ; toggle bit 18
        push    eax                                    
        popfd                      ; load modified eflags to CPU
        pushfd                     ; push eflags to look at
        pop     eax                ; get current eflags
        push    ebx                ; push original onto stack
        popfd                      ; restore original flags
        sti                        ; enable interrupts
        xor     eax, ebx           ; check if bit changed 
        jnz     up486              ; changed, so 486 or later
        mov     ax, 3              ; set 80386 flag
        jmp     uP_Exit

; 80486 test - Bit 21 in EFLAGS is not settable on a 486, but is
;   changeable on the Pentium CPU.  If bit 21 is changeable, it 
;   indicates the CPU supports the CPUID instruction.  It's 
;   amazing its only taken 10 years to implement the CPUID 
;   instruction, which should have been included from the start!  
;   During this test, we'll disable interrupts to ensure no 
;   interrupt will change any flags. 

up486:
        cli                        ; disable interrupts
        pushfd                     ; push flags to look at
        pop     eax                ; get eflags
        mov     ebx, eax           ; save for later
        xor     eax, 200000h       ; toggle bit 21
        push    eax                                    
        popfd                      ; load modified eflags to CPU
        pushfd                     ; push eflags to look at
        pop     eax                ; get current eflags
        push    ebx                ; push original onto stack
        popfd                      ; restore original flags
        sti                        ; enable interrupts
        xor     eax, ebx           ; check if bit changed 
        jnz     upPentium          ; changed, it's a Pentium
        mov     ax, 4              ; set 80486 flag
        jmp     uP_Exit

; Pentium - It's possible the CPUID instruction may appear on 
;   other CPU chips, so run the CPUID instruction to see what 
;   CPU type it indicates.  The CPUID returns a family number 
;   0 to 5 for the processor type.  As of this date, only the 
;   Pentium supports the CPUID instruction and it is assigned 
;   type 5.

upPentium:
        push    ecx                ; CPUID changes eax to edx
        push    edx
        mov     eax, 1             ; get family info function
        CPUID                      ; macro for CPUID instruction
        and     eax, 0F00h         ; find family info
        shr     eax, 8             ; move to al
        mov     ah, 1              ; set flag that CPUID ok
        pop     edx
        pop     ecx
       
up_Exit:
        pop     es
        pop     ds
        pop     dx
        pop     cx
        ret
cpuvalue endp
.8086                              ; return to 8086 instructions


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    HOOK INTERRUPT 6
;       Save the old interrupt 6 vector and replace it with  
;       a new vector to the bad_op_handler.  Vectors are handled 
;       directly without using DOS.
;
;       Called with:    nothing
;                      
;       Returns:        vector hooked
;                       old vector stored at
;                         ds:[old_int6_seg]
;                         ds:[old_int6_off]
;
;       Regs used:      none

hook_int6 proc    near
        push    ax
        push    cx
        push    es
        xor     ax, ax
        mov     es, ax
        cli                        ; disable interrupts
        mov     ax, es:[6*4]       ; get offset of int 6
        mov     cx, es:[6*4+2]     ; get segment
        mov     es:[6*4], offset bad_op_handler
        mov     word ptr es:[6*4+2], seg bad_op_handler
        sti                        ; enable interrupts
        mov     [old_int6_seg], cx ; save original vector
        mov     [old_int6_off], ax
        pop     es
        pop     cx
        pop     ax
        ret
hook_int6 endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    RESTORE INTERRUPT 6
;       Restore the previously saved old interrupt 6 vector.
;       Vectors handled directly without using DOS.
;
;       Called with:    old vector stored at
;                         ds:[old_int6_seg]
;                         ds:[old_int6_off]
;                      
;       Returns:        vector restored
;
;       Regs used:      none

restore_int6 proc    near
        push    ax
        push    cx
        push    dx
        mov     cx, [old_int6_seg] ; get original vector
        mov     dx, [old_int6_off]
        push    es
        xor     ax, ax
        mov     es, ax
        cli                        ; disable interrupts
        mov     es:[6*4], dx       ; restore original int 6
        mov     es:[6*4+2], cx
        sti                        ; enable interrupts
        pop     es
        pop     dx
        pop     cx
        pop     ax
        ret
restore_int6 endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    BAD OFFSET INTERRUPT HANDLER
;       If a bad opcode occurs (80286 or later) will come here.
;       The saved BADOFF offset is used to goto the routine
;       previously stored in BADOFF.
;
;       In a few cases, it is also used for double faults. A few
;       instructions (RDMSR & WRMSR) can issue a double fault if
;       not supported, so well come here as well.
;
;       Called with:    cs:[badoff] previously set
;
;       Returns:        returns to address stored in badoff


bad_op_handler proc far
        push    ax
        push    bp
        mov     ax, cs:[badoff]
        mov     bp, sp
        mov     ss:[bp+4], ax      ; insert new return offset
        pop     bp
        pop     ax
        iret
bad_op_handler endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    DECW
;       Convert the hex number in ax into decimal 1 to 5 ascii 
;       characters and insert into [di].  Increment di ptr.  The 
;       leading zeros are suppressed.
;
;       Called with:    ax = input hex number 
;                       di = pointer where to store characters
;                       bl = 0 for left justification 
;                            1 for no justification
;
;       Returns:        word converted to ascii at [di]
;
;       Regs used:      bx
;
;       Subs called:    hex2ascii

decw    proc    near
        push    ax
        push    cx
        push    dx
        cmp     ax, 0              ; check for zero
        jne     decskip0           ; jump if not
        mov     al, 4              ; if justify, make ax = 0
        mul     bl                 ;    no justify, ax = 4
        add     di, ax             ; move pointer
        mov     byte ptr [di], '0' ; put up ascii zero
        jmp     decskip15          ; done !

decskip0:
        xor     cl, cl             ; temp flag, 0=suppression on
        mov     ch, bl             ; save flag (0=left justify)
        xor     dx, dx             ; zero
        mov     bx, 10000
        div     bx                 ; (hex)/10000
        cmp     al, 0              ; 10000's ?
        je      decskip1           ; jump if zero
        inc     cl                 ; no longer zero suppression
        call    hex2ascii          ; convert to ascii
        mov     byte ptr [di], bh  ; put 1000's digit in
        jmp     decskip2
decskip1:
        cmp     ch, 0              ; left justify ?
        je      decskip3           ; jump if so
decskip2:
        inc     di
decskip3:
        mov     ax, dx             ; get remainder
        xor     dx, dx             ; zero
        mov     bx, 1000
        div     bx                 ; (hex)/1000
        cmp     cl, 0              ; zero suppression active ?
        jne     decskip3a          ; jump if not
        cmp     al, 0              ; 1000's ?
        je      decskip4           ; jump if zero
decskip3a:
        inc     cl                 ; no longer zero suppression
        call    hex2ascii          ; convert to ascii
        mov     byte ptr [di], bh  ; put 1000's digit in
        jmp     decskip5
decskip4:
        cmp     ch, 0              ; left justify ?
        je      decskip6           ; jump if so
decskip5:
        inc     di
decskip6:
        mov     ax, dx             ; get remainder
        xor     dx, dx             ; zero
        mov     bx, 100
        div     bx                 ; (remainder in dx)/100
        cmp     cl, 0              ; zero suppression active ?
        jne     decskip7           ; jump if not
        cmp     al, 0              ; zero ?
        je      decskip8           ; suppress zero
decskip7:
        inc     cl                 ; no longer zero suppression
        call    hex2ascii          ; convert to ascii
        mov     byte ptr [di], bh  ; put 100's digit in
        jmp     decskip9
decskip8:
        cmp     ch, 0              ; left justify ?
        je      decskip10          ; jump if so
decskip9:
        inc     di
decskip10:
        mov     ax, dx             ; get remainder
        xor     dx, dx             ; zero
        mov     bx, 10
        div     bx                 ; (remainder in dx)/10
        cmp     cl, 0              ; zero suppression active ?
        jne     decskip11          ; jump if not
        cmp     al, 0              ; zero ?
        je      decskip12          ; suppress zero
decskip11:
        call    hex2ascii          ; convert to ascii
        mov     byte ptr [di], bh  ; put 10's digit in
        jmp     decskip13
decskip12:
        cmp     ch, 0              ; left justify ?
        je      decskip14          ; jump if so
decskip13:
        inc     di
decskip14:
        mov     al, dl             ; get 1's digit (remainder)
        call    hex2ascii          ; convert to ascii
        mov     byte ptr [di], bh  ; put 1's digit in output
decskip15:
        inc     di
        pop     dx
        pop     cx
        pop     ax
        ret
decw    endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;   HEX2ASCII
;       Convert the hex number in al into two ascii characters
;
;       Called with:    al = input hex number
;
;       Returns         bx = converted digits in ascii
;
;       Regs Used:      al, bx

hex2ascii       proc    near
        mov     bl, al
        and     al, 0fh
        add     al, 90h
        daa
        adc     al, 40h
        daa
        mov     bh, al

        mov     al, bl             ; upper nibble
        shr     al, 1
        shr     al, 1
        shr     al, 1
        shr     al, 1
        and     al, 0fh
        add     al, 90h
        daa
        adc     al, 40h
        daa
        mov     bl, al             ; bx has two ascii bytes
        ret
hex2ascii       endp


cseg    ends

;================================================== stack ======

stacka  segment para stack

        db      192 dup (0)

stacka  ends

        end     start



